#!/sbin/openrc-run
# Copyright (c) 2007-2022 Funtoo Solutions, Inc.
# All rights reserved. Released under the 2-clause BSD license.

# TODO - generate cached config for /etc/init.d/foo, use them for starting and stopping
# rather than running "live" commands. This way "stop" will work correctly at all times.

interface=${RC_SVCNAME#netif.}

[ "$interface" = "tmpl" ] && exit 0

if [ -z "$template" ]; then
	eerror "Please specify a template (template=) for $RC_SVCNAME"
	exit 1
fi

CYAN="[36;01m"
OFF="[0m"

DESC2="${template}"
[ -n "$description" ] && DESC2="${description}"

STARTD="Network $DESC2 $CYAN$interface$OFF up"
STOPD="Network $DESC2 $CYAN$interface$OFF down"
if [ "$dhcp" = "yes" ]; then
	DHCPC=${DHCPC:-"/sbin/dhcpcd"}
	DHCPC_PIDFILE="/var/run/dhcpcd-${interface}.pid"
	if [ ! -f "${DHCPC}" ]; then
		echo "dhcpcd binary not found. Did you emerge net-misc/dhcpcd?"
		exit 1
	fi
fi

require() {
	local missing=""
	for envname in $*; do 
		[ -z "${!envname}" ] && missing="$missing $envname"
	done
	if [ -n "$missing" ]; then
		echo
		eerror "ERROR: Required configuration variable(s) are missing:"
		eerror ""
		for miss in $missing; do
			eerror "    ${CYAN}$miss${OFF}"
		done
		eerror ""
		eerror "Please correct your configuration to address this issue."
		echo
		exit 1
	fi
}

die() { eend 1 "$*"; exit 1; }
netif_depend() { return; }
netif_create() { return; }
netif_pre_up() { return; }
netif_post_up() { return; }
netif_pre_down() { return; }
netif_post_down() { return; }
netif_destroy() { return; }

try() {
	$* 
	if [ $? -ne 0 ]; then
		echo "Command failure: $*"
		exit 1
	fi
}

debug() {
	$* 
	if [ $? -ne 0 ]; then
		echo "Command failure: $*"
	fi
}

depend() {
	need localmount
	before net-online
	if [ "$vlanmode" != "off" ]; then
		if [ -e "/etc/init.d/netif.${trunk}" ]; then
			need "netif.$trunk"
		else
			need "$trunk"
		fi
	fi
	if [ -n "$gateway" ] || [ -n "$gateway6" ]; then
		provide net
	fi
	[ -n "$slaves" ] && need $(filter_interface_names $slaves)
	[ -n "$bridge" ] && need $bridge
	netif_depend
}

start() {
	ebegin "$STARTD"
	netif_create
	if [ "$macaddr" != "" -a "$vlanmode" != "sriov" ]; then
		/sbin/nameif ${interface} ${macaddr} || die "Couldn't rename interface"
	fi
	if [ ! -d /proc/sys/net/ipv6 ]; then
		# attempt to silently load ipv6 module
		modprobe -q ipv6
	fi
	if [ "$vlanmode" != "off" -a "$vlanmode" != "script" -a "$vlanmode" != "sriov" ]; then #configure vlan only if not managed by netif_* script or sriov
		if [ ! -d /proc/net/vlan ]; then
			modprobe -q 8021q
			if [ ! -d /proc/net/vlan ]; then
				eerror "VLAN for interface $interface could not be configured:"
				die " \n802.1q support is not present in this kernel."
			fi
		fi
		require trunk vlan
		trunk_int="$(get_trunk_int ${trunk})"
		ip link add name ${interface} link ${trunk_int} type vlan id ${vlan} || die "Couldn't configure VLAN ${vlan} on ${trunk}"
	elif [ "$vlanmode" = "sriov" ]; then
		trunk_int="$(get_trunk_int ${trunk})"
		if [ -n "$vlan" ]; then
			ip link set ${trunk_int} vf ${virtfn} vlan ${vlan} qos ${priority-0}
		fi
		if [ -z "$mac_replace" ]; then
			mac_replace="$macaddr"
		fi
		if [ -z "$mac_replace" ]; then
			trunk_mac="$(ip link show dev ${trunk_int} | awk '/link\/ether/{print $2}' || echo '00:00:00:00:00:00' )"
			mac_replace="$(get_virtfn_mac ${virtfn} ${trunk_mac})"
		fi
		ip link set ${trunk_int} vf ${virtfn} mac ${mac_replace}
		ip link set ${trunk_int} vf ${virtfn} spoofchk ${spoofchk-on}
		virtfn_dev="$(get_virtfn_dev ${trunk_int} ${virtfn})"
		if [ -z "$virtfn_dev" ]; then
			eend 0; return
		fi
		if [ "$virtfn_dev" != "$interface" ]; then
			/sbin/nameif ${interface} ${mac_replace}
		fi
	fi
	ip addr flush dev $interface scope global > /dev/null 2>&1
	set_sriov_numvfs "$numvfs"
	netif_pre_up
	if [ -n "$mac_replace" -a "$vlanmode" != "sriov" ]; then
		ip link set dev $interface address $mac_replace || die "Couldn't set MAC to $mac_replace."
	fi
	debug ip link set $interface up
	if [ -n "$mtu" ]; then
		ip link set $interface mtu $mtu
		if [ -n "$slaves" ]; then
			for slave in ${slaves//netif./}; do
				ip link set $slave mtu $mtu || eend $?
			done
		fi
	fi
	if [ -n "$bridge" ]; then
		ip link set dev $interface mtu $(ip link show dev $(get_interface_names $bridge) | sed -re 's/^.*mtu ([0-9]+).*$/\1/; q')
		brctl addif $(get_interface_names $bridge) $interface || die "brctl addif error"
	fi
	ezdhcp start
	ezroute 4 add; ezroute 6 add; ezresolv add
	ezrule 4 add; ezrule 6 add
	if [ "$multicast" = "yes" ]
	then
		ebegin "Adding multicast route for $interface"	
		route add -net 224.0.0.0 netmask 240.0.0.0 dev $interface
		eend $?
	fi
	netif_post_up
	if [ -n "$virtfn" ]; then
		trunk_int="$(get_trunk_int ${trunk})"
		if ! get_virtfn_dev >/dev/null ; then
			ip link set ${trunk_int} vf ${virtfn} vlan 0
			ip link set ${trunk_int} vf ${virtfn} spoofchk on
			eend 0; return
		fi
	fi
	eend $?
}

stop() {
	ebegin "$STOPD"
	set_sriov_numvfs 0
	netif_pre_down
	ezrule 6 del; ezrule 4 del
	ezresolv del; ezroute 6 del; ezroute 4 del
	ezdhcp stop
	ip link set $interface down
	ip addr flush dev $interface scope global
	netif_post_down
	# VLANS:
	[ "${vlanmode}" != "off" -a "$vlanmode" != "script" -a "$vlanmode" != "sriov" ] && ip link delete ${interface}
	if [ "$vlanmode" == "sriov" ]; then
		ip link set ${trunk_int} vf ${virtfn} vlan 0
		ip link set ${trunk_int} vf ${virtfn} spoofchk on
		ip link set ${trunk_int} vf ${virtfn} mac "00:00:00:00:00:00"
	fi
	netif_destroy
	eend 0
}

# DOM, IP, NM, SLAVES, MTU, NS1, NS2, GW are deprecated - this is backwards compat. code:
domain=${domain:-$DOM}
if [ -n "$IP" ]; then
	ipaddr=$IP/$NM
fi
netmask=${netmask:-$NM}
slaves=${slaves:-$SLAVES}
mtu=${mtu:-$MTU}
nameservers="${nameservers:-$NS1 $NS2}"
gateway=${gateway:-$GW}
# COMPAT END

get_ipnm_part() {
	echo "${1%%;*}"
}

# if_ipv4 will accept something like 1.2.3.4/24;broadcast=1.2.3.255 and return a non-null string
# if the address appears to be an ipv4 address.

if_ipv4() {
	echo "${1%%/*}" | awk '/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/ {print $1}'
}

# we encode the broadcast like this: 1.2.3.4/24;broadcast=1.2.3.255;foo=bar;x;y;z
# we call the following function like this:
# 'get_option "$ipnm" broadcast +'
# it will return "1.2.3.255" if defined, and a '+' if not defined.
# 'get_option "1.2.3.4;a;b;c" a false' will return 'true' if "a" is defined as a keyword, and "false" (default value)
# otherwise.

get_option() {
	local full="$1"
	local ipnm="$(get_ipnm_part $full)"
	local oldparts="${full:${#ipnm}}"
	local trim
	while true; do
		[ "${oldparts:$((${#oldparts}-1))}" = ";" ] && oldparts="${oldparts:0:$((${#oldparts}-1))}" && continue
		count=$(($count + 1))
		thispart="${oldparts##*;}"
		varname="${thispart%%=*}"
		if [ "$varname" = "$thispart" ] && [ "$thispart" = "$2" ]; then
			echo "true"
			return
		fi
		if [ "$varname" != "$thispart" ] && [ "$varname" = "$2" ]; then
			echo "${thispart#*=}"
			return
		fi
		[ -z "$thispart" ] && break
		trim=$(( ${#oldparts} - ${#thispart} ))
		oldparts="${oldparts:0:${trim}}"
		[ -z "${oldparts}" ] && break
	done
	# return default (if any)
	echo "$3"
}

# This removes the leading part of the network script, up to and including the first '.', to grab the interface name

get_interface_names() {
	for ifn in $*; do
		echo ${ifn#netif.}
	done
}

# This removes any non-netif interface names from a list:

filter_interface_names() {
	for ifn in $*; do
		[ "${ifn#netif.}" != "${ifn}" ] && echo "$ifn"
	done
}


# We are migrating from having trunk=eth0 to trunk=net.eth0. This following function is used to implement backwards
# compatibility. If we see $trunk existing in /etc/init.d, then we assume it has a net. or netif. prefix we need to
# remove to get the interface name. Otherwise, we assume it contains the interface name itself, such as eth0, and
# use it as-is.

get_trunk_int() {
	if [ -e /etc/init.d/$1 ]; then
		echo $(get_interface_names $1)
	else
		echo $1
	fi
}

# derive a default fixed mac address for sr-iov virtual functions by setting the
# first octet to indicate a locally administered mac, the second two octets to encode
# the virtual function number in hex (allowing up to 16K VF's) and repeating the 
# physical function's last three octets so that the physical device is recognizable

# arg1: virtfn number
# arg2: physfn mac address
get_virtfn_mac() {
	local tmp2="$(( ( $1 & 0xFF00 ) >> 8 ))"
	local tmp3="$(( $1 & 0xFF ))"
	local oct1="02"
	local oct2="$(printf "%02x" $tmp2)"
	local oct3="$(printf "%02x" $tmp3)"
	local oct4="$(echo $2 | cut -f4 -d:)"
	local oct5="$(echo $2 | cut -f5 -d:)"
	local oct6="$(echo $2 | cut -f6 -d:)"
	echo "$oct1:$oct2:$oct3:$oct4:$oct5:$oct6"
}

# find out the logical device name of the given virtual function
# arg1: trunk_int
# arg2: virtfn number
get_virtfn_dev() {
	if [ -d "/sys/class/net" ]; then
		local virtnetpath="/sys/class/net/$1/device/virtfn$2/net"
		if [ ! -d "$virtnetpath" ]; then
			return 1
		fi
		ls /sys/class/net/$1/device/virtfn$2/net | head -n 1
	else
		echo "Funtoo cannot handle SR-IOV networking on non-Linux kernels." >&2
		echo "If this message surprises you, check that /sys/ is mounted." >&2
		exit 1
	fi
}

# set the number of active virtual functions
# arg1: integer
set_sriov_numvfs() {
	local wanted="$1"
	[ -z "$wanted" -o -z "$maxvfs" ] && return 0
	local target="/sys/class/net/${interface}/device/sriov_numvfs"
	local current="$(cat "$target" 2>/dev/null)"
	[ -z "$current" -o "$wanted" == "$current" ] && return 0
	echo 0 > "$target"
	[ "$wanted" -eq 0 ] && return 0
	echo "$wanted" > "$target"
	return $?
}

start_pre() {
	if [ -n "$ipaddr $ipaddrs" ]; then
		for i in $ipaddr $ipaddrs; do
			if [ "${i##*/}" = "$i" ]; then
				echo
				ewarn "You probably want to add a netmask to the ipaddr $i defined in your configuration."
				ewarn "Example: ipaddr=\"192.168.0.1/24\""
				echo
			fi
		done
	fi
}

ezdhcp() {
	if [ "$dhcp" != "yes" ]; then
		return
	fi
	if [ "$1" = "start" ]; then
		if [ -f "${DHCPC_PIDFILE}" ]; then
			die "There's a PID file for interface $interface. Aborting."
		fi
		$DHCPC -b -t0 $interface
	else
		if [ ! -f "${DHCPC_PIDFILE}" ]; then
			die "There's no PID file for interface ${interface}. Aborting."
		fi
		start-stop-daemon --stop --exec "${DHCPC}" --pidfile "${DHCPC_PIDFILE}"
	fi
}

ezdns() {
	# This function generates a resolv.conf file, which ezresolv() passes to resolvconf
	[ -n "$domain" ] && echo "domain $domain"
	[ -n "$search" ] && echo "search $search"
	for ns in $nameservers; do
		echo "nameserver $ns"
	done
}

ezresolv() {
	# This function calls resolvconf (openresolv) with the correct resolv.conf passed as a here-file
	if [ "$1" = "add" ]; then
		[ -z "`ezdns`" ] && return
		resolvconf -a $interface << EOF || die "Problem adding DNS info for $interface"
`ezdns`
EOF
	else
		resolvconf -d $interface > /dev/null 2>&1
	fi
}

ezroute() {
	# This function processes a semi-colon delimited list of routes set in $route, and also
	# supports the $gateway variable for setting a default IP route. 
	local rest
	if [ "$1" = "4" ]; then
		rest="$route"
		[ -n "$gateway" ] && rest="$rest; default via $gateway dev $interface"
	elif [ "$1" = "6" ]; then
		rest="$route6"
		[ -n "$gateway6" ] && rest="$rest; default via $gateway6 dev $interface"
	fi
	if [ -n "$rest" ]; then
		# must end in a semi-colon before we start processing:
		rest="$rest;"
		while [ "$rest" != "" ]
		do
			# first = current route command; rest = remainder
			first="${rest%%;*}"
			rest="${rest#*;}"
			# trim any trailing or leading spaces:
			first=${first%% }; first=${first## }
			[ "$first" != "" ] && ip -$1 route $2 $first
			[ "$2" = "add" ] && [ $? -ne 0 ] && die "Couldn't set route: $first"
		done
	fi
}

ezrule() {
	# This function processes a semi-colon delimited list of rules set in $rule
	local rest
	if [ "$1" = "4" ]; then
		rest="$rule"
	elif [ "$1" = "6" ]; then
		rest="$rule6"
	fi
	if [ -n "$rest" ]; then
		# ensure a terminating semi-colon
		rest="$rest;"
		while [ "$rest" != "" ]; do
			first="${rest%%;*}"
			rest="${rest#*;}"
			first=${first%% }; first=${first## }
			[ "$first" != "" ] && ip -$1 rule $2 $first
			[ "$2" = "add" ] && [ $? -ne 0 ] && die "Couldn't set rule: $first"
		done
	fi
}

if [ -z "$virtfn" -a -z "$trunk" -a -z "$vlanmode" ]; then
	# auto-detect trunk/vlan ID from interface name
	vlanmode="auto"
	vlan="${interface##*.}"
	trunk="${interface%.*}"
	[ "$vlan" = "$interface" ] && vlanmode="off"
elif [ -n "$virtfn" ]; then
	# trunk/vlan ID configures sr-iov virtual function
	vlanmode="sriov"
	require trunk
else
	# trunk/vlan ID specified by user in config
	: ${vlanmode:="custom"} #don't overwrite vlanmode if already specified
	require vlan
fi

# skip this check when /sys isn't mounted yet
if [ -n "$numvfs" -a -d "/sys/class/net" ]; then
	if [ "$vlanmode" != "off" ]; then
		echo "ERROR: cannot set up SR-IOV from a VLAN interface." >&2
		exit 1
	fi
	maxvfs="$(cat /sys/class/net/${interface}/device/sriov_totalvfs 2>/dev/null)"
	if [ -z "$maxvfs" -a -n "$numvfs" ]; then
		echo "ERROR: ${interface} does not support virtual functions."
		exit 1
	fi
	if [ "$numvfs" -lt 1 -o "$numvfs" -gt "$maxvfs" ]; then
		numvfs="$maxvfs"
	fi
fi

. /etc/netif.d/$template
